package edu.temple.templepatco;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.Scanner;
import java.util.TreeMap;

import android.util.Log;

/**
 * This class has a number of static utility functions for calculating the routes
 */
public class Router {

	/**
	 * Holds the PATCO schedules in memory so the csv files don't need to be parsed on every refresh
	 */
	protected static Map<Integer, ArrayList<Stop[]>> schedulePATCO;

	/**
	 * Adds a list of Stop to a statically accessible Map using the day of the
	 * week as the key
	 * 
	 * @param sched
	 *            The array of Stop
	 * @param dayOfWeek
	 *            Which day of the week this list of Stop is associated with
	 */
	protected static void setPATCOSchedule(ArrayList<Stop[]> sched, int dayOfWeek) {
		if (schedulePATCO == null) {
			schedulePATCO = new TreeMap<Integer, ArrayList<Stop[]>>();
		}
		schedulePATCO.put(dayOfWeek, sched);
	}

	/**
	 * Reads the PATCO schedules from an InputStream and returns a list of
	 * stops. It only extracts the first, fourth, and fifth value from each row
	 * of the input file.
	 * 
	 * @param in
	 *            An open InputStream to the source file, typically one of the
	 *            csv files embedded in the App
	 * @return An array of Stop in the order they appear in the input file
	 */
	protected static ArrayList<Stop[]> readPATCOSchedule(InputStream in) {
		Scanner scan = new Scanner(in);
		String[] oneLine;
		ArrayList<Stop[]> schedule = new ArrayList<Stop[]>();
		DateFormat formatter = new SimpleDateFormat("h:mm a");
		Calendar current = Calendar.getInstance();
		Calendar cal = Calendar.getInstance();
		Date date;
		Stop[] oneTrain;

		while (scan.hasNext()) {
			oneLine = scan.nextLine().split(",");
			if (oneLine.length > 0) {
				oneTrain = new Stop[3];
				try {
					date = formatter.parse(oneLine[0]);
					cal.setTime(date);
					cal.set(current.get(Calendar.YEAR), current.get(Calendar.MONTH), current.get(Calendar.DATE));
					oneTrain[0] = new Stop("board PATCO-15-16th Locust", cal.getTime());
					date = formatter.parse(oneLine[3]);
					cal.setTime(date);
					cal.set(current.get(Calendar.YEAR), current.get(Calendar.MONTH), current.get(Calendar.DATE));

					oneTrain[1] = new Stop("board PATCO-8th and Market", cal.getTime());
					date = formatter.parse(oneLine[4]);
					cal.setTime(date);
					cal.set(current.get(Calendar.YEAR), current.get(Calendar.MONTH), current.get(Calendar.DATE));
					oneTrain[2] = new Stop("arrive Camden City Hall", cal.getTime());
				} catch (ParseException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				schedule.add(oneTrain);
			}
		}
		Log.i("Temple-PATCO", "Found " + schedule.size() + " PATCO trains.");
		return schedule;
	}

	/**
	 * Calculates all five routes for the given request time
	 * 
	 * @param calendar
	 *            A Calendar that represents when the request was made
	 * @return An ArrayList containing five Route objects, one for each of five
	 *         possible paths
	 * @throws ParseException
	 *             Thrown if the method has a problem handling the HTML from
	 *             SEPTA's website
	 */
	public static ArrayList<Route> findRoute(Calendar calendar) throws ParseException {
		ArrayList<Route> Routes = new ArrayList<Route>();
		BufferedReader in;
		URL SeptaBroad;
		String[] holdCecil = null;
		String[] holdGirard = null;
		String[] hold8th = null;
		String[] holdCityBroad = null;
		String[] hold15th = null;
		String[] hold8thMarket = null;
		String[] holdWalnut = null;
		int day = calendar.get(Calendar.DAY_OF_WEEK);
		String BroadURL;
		String MarketURL;
		if (day == Calendar.SUNDAY) {
			Log.i("Temple-PATCO", "Sunday");
			BroadURL = "http://www.septa.org/m/schedules/transit/h/BSS_0.htm";
			MarketURL = "http://www.septa.org/m/schedules/transit/h/MFL_0.htm";
		} else if (day == Calendar.SATURDAY) {
			Log.i("Temple-PATCO", "Saturday");
			BroadURL = "http://www.septa.org/m/schedules/transit/s/BSS_0.htm";
			MarketURL = "http://www.septa.org/m/schedules/transit/s/MFL_0.htm";
		} else {
			Log.i("Temple-PATCO", "Week Day");
			BroadURL = "http://www.septa.org/m/schedules/transit/w/BSS_0.htm";
			MarketURL = "http://www.septa.org/m/schedules/transit/w/MFL_0.htm";
		}
		try {
			SeptaBroad = new URL(BroadURL);

			in = new BufferedReader(new InputStreamReader(SeptaBroad.openStream()));

			String inputLine;
			while ((inputLine = in.readLine()) != null) {
				if (inputLine
						.contains("class=\"yellowdata\"height=\"30px\"style=\"vertical-align:middle;\" title=\"Cecil B Moore\"")) {
					inputLine = in.readLine();
					inputLine = inputLine.replace("<td>", "").replaceAll("<tr>", "").replace("</tr></table>", "");
					inputLine = inputLine.replace("</tr>", "").replace("</td>", "");
					inputLine = inputLine.replace("<table width=\"100%\">", "");
					inputLine = inputLine.replace("&mdash", "---").replace("&nbsp", "");
					inputLine = inputLine.replace("<b>", "").replace("</b>", "");
					holdCecil = inputLine.split(";");
				}
				if (inputLine
						.contains("class=\"bluedata\"height=\"30px\"style=\"vertical-align:middle;\" title=\"Girard\">")) {
					inputLine = in.readLine();
					inputLine = inputLine.replace("<td>", "").replaceAll("<tr>", "").replace("</tr></table>", "");
					inputLine = inputLine.replace("</tr>", "").replace("</td>", "");
					inputLine = inputLine.replace("<table width=\"100%\">", "");
					inputLine = inputLine.replace("&mdash", "---").replace("&nbsp", "");
					inputLine = inputLine.replace("<b>", "").replace("</b>", "");
					holdGirard = inputLine.split(";");
				}
				if (inputLine
						.contains("class=\"yellowdata\"height=\"30px\"style=\"vertical-align:middle;\" title=\"8th St &amp; Market St\">")) {
					inputLine = in.readLine();
					inputLine = inputLine.replace("<td>", "").replaceAll("<tr>", "").replace("</tr></table>", "");
					inputLine = inputLine.replace("</tr>", "").replace("</td>", "");
					inputLine = inputLine.replace("<table width=\"100%\">", "");
					inputLine = inputLine.replace("&mdash", "---").replace("&nbsp", "");
					inputLine = inputLine.replace("<b>", "").replace("</b>", "");
					hold8th = inputLine.split(";");
				}
				if (inputLine
						.contains("class=\"bluedata\"height=\"30px\"style=\"vertical-align:middle;\" title=\"City Hall\">")) {
					inputLine = in.readLine();
					inputLine = inputLine.replace("<td>", "").replaceAll("<tr>", "").replace("</tr></table>", "");
					inputLine = inputLine.replace("</tr>", "").replace("</td>", "");
					inputLine = inputLine.replace("<table width=\"100%\">", "");
					inputLine = inputLine.replace("&mdash", "---").replace("&nbsp", "");
					inputLine = inputLine.replace("<b>", "").replace("</b>", "");
					holdCityBroad = inputLine.split(";");
				}
				if (inputLine
						.contains("class=\"yellowdata\"height=\"30px\"style=\"vertical-align:middle;\" title=\"Walnut-Locust\">")) {
					inputLine = in.readLine();
					inputLine = inputLine.replace("<td>", "").replaceAll("<tr>", "").replace("</tr></table>", "");
					inputLine = inputLine.replace("</tr>", "").replace("</td>", "");
					inputLine = inputLine.replace("<table width=\"100%\">", "");
					inputLine = inputLine.replace("&mdash", "---").replace("&nbsp", "");
					inputLine = inputLine.replace("<b>", "").replace("</b>", "");
					holdWalnut = inputLine.split(";");
				}

			}
			in.close();
		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		try {
			SeptaBroad = new URL(MarketURL);
			in = new BufferedReader(new InputStreamReader(SeptaBroad.openStream()));
			String inputLine;
			while ((inputLine = in.readLine()) != null) {
				if (inputLine
						.contains("class=\"bluedata\"height=\"30px\"style=\"vertical-align:middle;\" title=\"15th St\"")) {
					inputLine = in.readLine();
					inputLine = inputLine.replace("<td>", "").replaceAll("<tr>", "").replace("</tr></table>", "");
					inputLine = inputLine.replace("</tr>", "").replace("</td>", "");
					inputLine = inputLine.replace("<table width=\"100%\">", "");
					inputLine = inputLine.replace("&mdash", "---").replace("&nbsp", "");
					inputLine = inputLine.replace("<b>", "").replace("</b>", "");
					hold15th = inputLine.split(";");
				}
				if (inputLine
						.contains("class=\"yellowdata\"height=\"30px\"style=\"vertical-align:middle;\" title=\"8th St\">")) {
					inputLine = in.readLine();
					inputLine = inputLine.replace("<td>", "").replaceAll("<tr>", "").replace("</tr></table>", "");
					inputLine = inputLine.replace("</tr>", "").replace("</td>", "");
					inputLine = inputLine.replace("<table width=\"100%\">", "");
					inputLine = inputLine.replace("&mdash", "---").replace("&nbsp", "");
					inputLine = inputLine.replace("<b>", "").replace("</b>", "");
					hold8thMarket = inputLine.split(";");
				}
			}
			in.close();
		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		Date[] CecilTimes = null;
		Date[] GirardTimes = null;
		Date[] Broad8thTimes = null;
		Date[] Times15th = null;
		Date[] CityTimes = null;
		Date[] Times8th = null;
		Date[] WalnutTimes = null;
		if (holdCecil != null) {
			CecilTimes = ConvertToDate(holdCecil, calendar.getTime());
		}
		if (holdGirard != null) {
			GirardTimes = ConvertToDate(holdGirard, calendar.getTime());
		}
		if (hold8th != null) {
			Broad8thTimes = ConvertToDate(hold8th, calendar.getTime());
		}
		if (hold15th != null) {
			Times15th = ConvertToDate(hold15th, calendar.getTime());
		}
		if (holdCityBroad != null) {
			CityTimes = ConvertToDate(holdCityBroad, calendar.getTime());
		}
		if (hold8thMarket != null) {
			Times8th = ConvertToDate(hold8thMarket, calendar.getTime());
		}
		if (holdWalnut != null) {
			WalnutTimes = ConvertToDate(holdWalnut, calendar.getTime());
		}
		Route r = null;
		r = new Route(BroadRidgeSpur(CecilTimes, GirardTimes, Broad8thTimes), "Broad-Ridge spur", calendar.getTime());
		if (r.getEndTime() != null) {
			Routes.add(r);
		}
		r = new Route(BroadLocalToExpressToMarket(CecilTimes, GirardTimes, CityTimes, Times15th, Times8th),
				"Express -> MFL", calendar.getTime());
		if (r.getEndTime() != null) {
			Routes.add(r);
		}
		r = new Route(BroadLocalToCityToMarket(CecilTimes, CityTimes, Times15th, Times8th), "Local -> MFL",
				calendar.getTime());
		if (r.getEndTime() != null) {
			Routes.add(r);
		}
		r = new Route(BroadLocalToExpressToWalnut(CecilTimes, GirardTimes, WalnutTimes), "Express to Walnut-Locust",
				calendar.getTime());
		if (r.getEndTime() != null) {
			Routes.add(r);
		}
		r = new Route(BroadLocalToWalnut(CecilTimes, WalnutTimes), "Local to Walnut-Locust", calendar.getTime());
		if (r.getEndTime() != null) {
			Routes.add(r);
		}
		return Routes;
	}

	/**
	 * Converts a row of times into Date objects
	 * 
	 * @param holdStrings
	 *            An array of String that each contain one time
	 * @param reqTime
	 *            The time that the refresh was requested, times earlier than
	 *            this one will be ignored
	 * @return An array of Date objects representing the times from the hold
	 *         string, the day/month/year is set to the current day/month/year
	 */
	private static Date[] ConvertToDate(String holdStrings[], Date reqTime) {
		Date[] times = new Date[holdStrings.length];
		DateFormat formatter = new SimpleDateFormat("hh:mma");
		Calendar current = Calendar.getInstance();
		current.setTime(reqTime);
		for (int j = 1; j < holdStrings.length - 1; j++) {
			if (!holdStrings[j].equals("---")) {
				Date date = null;
				try {
					date = formatter.parse(holdStrings[j]);
				} catch (ParseException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				Calendar cal = Calendar.getInstance();
				cal.setTime(date);
				cal.set(current.get(Calendar.YEAR), current.get(Calendar.MONTH), current.get(Calendar.DATE));
				if (0 < cal.compareTo(current)) {
					times[j] = cal.getTime();
				}
			}
		}
		return times;

	}

	/**
	 * Finds the soonest route that switches from the BSL local to the Broad
	 * Ridge Spur at Girard
	 * 
	 * @param CecilTimes
	 *            All of the Cecil B. Moore stops to consider
	 * @param GirardTimes
	 *            All of the Girard stops to consider
	 * @param Broad8thTimes
	 *            All of the Broad Ridge Spur stops to consider
	 * @return A list of the stops taken by the fastest route starting at Cecil
	 *         B. and ending at Camden
	 */
	private static Stop[] BroadRidgeSpur(Date[] CecilTimes, Date[] GirardTimes, Date[] Broad8thTimes) {
		Calendar test = Calendar.getInstance();
		ArrayList<Stop[]> PATCOtoday = schedulePATCO.get(test.get(Calendar.DAY_OF_WEEK));
		for (int i = 0; i < CecilTimes.length; i++) {
			if (CecilTimes[i] != null) {
				if (GirardTimes[i] != null) {
					for (int j = i; j < CecilTimes.length; j++) {
						if (GirardTimes[j] != null) {
							if (0 > GirardTimes[i].compareTo(GirardTimes[j])) {
								if (Broad8thTimes[j] != null) {
									for (int k = 0; k < PATCOtoday.size(); k++) {
										if (0 > Broad8thTimes[j].compareTo(PATCOtoday.get(k)[1].getTime())) {
											Stop[] returnarray = {
													new Stop("board Local-Cecil B. Moore", CecilTimes[i]),
													new Stop("disembark at Girard", GirardTimes[i]),
													new Stop("board Broad Ridge Spur-Girard", GirardTimes[j]),
													new Stop("disembark at 8th and Market", Broad8thTimes[j]),
													PATCOtoday.get(k)[1], PATCOtoday.get(k)[2] };
											return returnarray;
										}
									}
								}
							}
						}
					}
				}
			}
		}
		return null;
	}

	/**
	 * Finds the soonest route that switches from the BSL local to express at
	 * Girard and then switched to the MFL at City Hall
	 * 
	 * @param CecilTimes
	 *            All of the Cecil B. Moore stops to consider
	 * @param GirardTimes
	 *            All of the Girard stops to consider
	 * @param CityTimes
	 *            All of the City Hall stops to consider
	 * @param Times15th
	 *            All of the MFL City Hall stops to consider
	 * @param Times8th
	 *            All of the MFL 8th and Market stops to consider
	 * @return A list of the stops taken by the fastest route starting at Cecil
	 *         B. and ending at Camden
	 */
	private static Stop[] BroadLocalToExpressToMarket(Date[] CecilTimes, Date[] GirardTimes, Date[] CityTimes,
			Date[] Times15th, Date[] Times8th) {
		Calendar test = Calendar.getInstance();
		ArrayList<Stop[]> PATCOtoday = schedulePATCO.get(test.get(Calendar.DAY_OF_WEEK));
		Calendar transfer;
		for (int i = 0; i < CecilTimes.length; i++) {
			if (CecilTimes[i] != null) {
				if (GirardTimes[i] != null) {
					for (int j = i; j < CecilTimes.length; j++) {
						if (GirardTimes[j] != null) {
							if (0 > GirardTimes[i].compareTo(GirardTimes[j])) {
								if (CecilTimes[j] == null) {
									if (CityTimes[j] != null) {
										// add in 3 minute delay to switch from
										// BSL to MFL
										transfer = Calendar.getInstance();
										transfer.setTime(CityTimes[j]);
										transfer.add(Calendar.MINUTE, 3);
										for (int k = 0; k < Times15th.length; k++) {
											if (Times15th[k] != null) {
												if (0 > transfer.getTime().compareTo(Times15th[k])) {
													for (int l = 0; l < PATCOtoday.size(); l++) {
														if (0 > Times8th[k].compareTo(PATCOtoday.get(l)[1].getTime())) {
															Stop[] returnarray = {
																	new Stop("board Local-Cecil B. Moore",
																			CecilTimes[i]),
																	new Stop("disembark at Girard", GirardTimes[i]),
																	new Stop("board Express-Girard", GirardTimes[j]),
																	new Stop("disembark at City Hall", CityTimes[j]),
																	new Stop("board Market Frankfort-15th Street",
																			Times15th[k]),
																	new Stop("disembark at 8th and Market", Times8th[k]),
																	PATCOtoday.get(l)[1], PATCOtoday.get(l)[2] };
															return returnarray;
														}
													}
												}
											}
										}
									}
								}
							}
						}
					}
				}
			}
		}
		return null;
	}

	/**
	 * Finds the fastest route using the BSL local and then switching to the MFL
	 * at City Hall
	 * 
	 * @param CecilTimes
	 *            All of the Cecil B. Moore stops to consider
	 * @param CityTimes
	 *            All of the City Hall stops to consider
	 * @param Times15th
	 *            All of the MFL City Hall stops to consider
	 * @param Times8th
	 *            All of the MFL 8th and Market stops to consider
	 * @return A list of the stops taken by the fastest route starting at Cecil
	 *         B. and ending at Camden
	 */
	private static Stop[] BroadLocalToCityToMarket(Date[] CecilTimes, Date[] CityTimes, Date[] Times15th,
			Date[] Times8th) {
		Calendar test = Calendar.getInstance();
		ArrayList<Stop[]> PATCOtoday = schedulePATCO.get(test.get(Calendar.DAY_OF_WEEK));
		Calendar transfer;
		for (int i = 0; i < CecilTimes.length; i++) {
			if (CecilTimes[i] != null) {
				if (CityTimes[i] != null) {
					// add in 3 minute delay to switch from BSL to MFL
					transfer = Calendar.getInstance();
					transfer.setTime(CityTimes[i]);
					transfer.add(Calendar.MINUTE, 3);
					for (int j = 0; j < Times15th.length; j++) {
						if (Times15th[j] != null) {
							if (0 > transfer.getTime().compareTo(Times15th[j])) {
								for (int l = 0; l < PATCOtoday.size(); l++) {
									if (0 > Times8th[j].compareTo(PATCOtoday.get(l)[1].getTime())) {
										Stop[] returnarray = { new Stop("board Local-Cecil B. Moore", CecilTimes[i]),
												new Stop("disembark at City Hall", CityTimes[i]),
												new Stop("board Market Frankfort-15th Street", Times15th[j]),
												new Stop("disembark at 8th and Market", Times8th[j]),
												PATCOtoday.get(l)[1], PATCOtoday.get(l)[2] };
										return returnarray;
									}
								}
							}
						}
					}
				}
			}
		}
		return null;
	}

	/**
	 * Finds the fastest route switching to the express at Girard and then
	 * switching from the BSL to PATCO at Walnut-Locust
	 * 
	 * @param CecilTimes
	 *            All of the Cecil B. Moore stops to consider
	 * @param GirardTimes
	 *            All of the Girard stops to consider
	 * @param WalnutTimes
	 *            All of the Walnut-Locust stops to consider
	 * @return A list of the stops taken by the fastest route starting at Cecil
	 *         B. and ending at Camden
	 */
	private static Stop[] BroadLocalToExpressToWalnut(Date[] CecilTimes, Date[] GirardTimes, Date[] WalnutTimes) {
		Calendar test = Calendar.getInstance();
		ArrayList<Stop[]> PATCOtoday = schedulePATCO.get(test.get(Calendar.DAY_OF_WEEK));
		Calendar transfer;

		for (int i = 0; i < CecilTimes.length; i++) {
			if (CecilTimes[i] != null) {
				if (GirardTimes[i] != null) {
					for (int j = i; j < CecilTimes.length; j++) {
						if (GirardTimes[j] != null) {
							if (0 > GirardTimes[i].compareTo(GirardTimes[j])) {
								if (CecilTimes[j] == null) {
									for (int k = j; k < CecilTimes.length; k++) {
										if (WalnutTimes[k] != null) {
											if (0 > GirardTimes[j].compareTo(WalnutTimes[k])) {
												// add in 5 minute delay to
												// switch
												// from BSL to PATCO
												transfer = Calendar.getInstance();
												transfer.setTime(WalnutTimes[k]);
												transfer.add(Calendar.MINUTE, 5);
												for (int l = 0; l < PATCOtoday.size(); l++) {
													if (0 > transfer.getTime()
															.compareTo(PATCOtoday.get(l)[0].getTime())) {
														Stop[] returnarray = {
																new Stop("board Local-Cecil B. Moore", CecilTimes[i]),
																new Stop("disembark at Girard", GirardTimes[i]),
																new Stop("board Express-Girard", GirardTimes[j]),
																new Stop("disembark at Walnut Locust", WalnutTimes[k]),
																PATCOtoday.get(l)[0], PATCOtoday.get(l)[2] };
														return returnarray;
													}
												}
											}
										}
									}
								}
							}
						}
					}
				}
			}
		}
		return null;
	}

	/**
	 * Finds the fastest route taking the local to Walnut-Locust and then
	 * switching to PATCO there
	 * 
	 * @param CecilTimes
	 *            All of the Cecil B. Moore stops to consider
	 * @param WalnutTimes
	 *            All of the Walnut-Locust stops to consider
	 * @return A list of the stops taken by the fastest route starting at Cecil
	 *         B. and ending at Camden
	 */
	private static Stop[] BroadLocalToWalnut(Date[] CecilTimes, Date[] WalnutTimes) {
		Calendar test = Calendar.getInstance();
		ArrayList<Stop[]> PATCOtoday = schedulePATCO.get(test.get(Calendar.DAY_OF_WEEK));
		Calendar transfer;
		for (int i = 0; i < CecilTimes.length; i++) {
			if (CecilTimes[i] != null) {
				if (WalnutTimes[i] != null) {
					// add in 5 minute delay to switch from BSL to PATCO
					transfer = Calendar.getInstance();
					transfer.setTime(WalnutTimes[i]);
					transfer.add(Calendar.MINUTE, 5);
					for (int l = 0; l < PATCOtoday.size(); l++) {
						if (0 > transfer.getTime().compareTo(PATCOtoday.get(l)[0].getTime())) {

							Stop[] returnarray = { new Stop("board Local-Cecil B. Moore", CecilTimes[i]),
									new Stop("disembark at Walnut Locust", WalnutTimes[i]), PATCOtoday.get(l)[0],
									PATCOtoday.get(l)[2] };
							return returnarray;
						}
					}
				}
			}
		}
		return null;
	}

}
